{#     Copyright 2020, Kay Hayen, mailto:kay.hayen@gmail.com                    #}
{#                                                                              #}
{#     Part of "Nuitka", an optimizing Python compiler that is compatible and   #}
{#     integrates with CPython, but also works on its own.                      #}
{#                                                                              #}
{#     Licensed under the Apache License, Version 2.0 (the "License");          #}
{#     you may not use this file except in compliance with the License.         #}
{#     You may obtain a copy of the License at                                  #}
{#                                                                              #}
{#        http://www.apache.org/licenses/LICENSE-2.0                            #}
{#                                                                              #}
{#     Unless required by applicable law or agreed to in writing, software      #}
{#     distributed under the License is distributed on an "AS IS" BASIS,        #}
{#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #}
{#     See the License for the specific language governing permissions and      #}
{#     limitations under the License.                                           #}
{#                                                                              #}
static inline bool _BINARY_OPERATION_{{op_code}}_{{left.getHelperCodeName()}}_{{right.getHelperCodeName()}}_INPLACE({{left.getVariableDecl("*operand1")}}, {{right.getVariableDecl("operand2")}}) {
    assert(operand1); // Pointer must be non-null.

    {{left.getCheckValueCode("*operand1")}}
    {{right.getCheckValueCode("operand2")}}

{# CPython2 treats integer values with fast path. #}
{% if left.mayBothHaveType(right, "int") %}
#if PYTHON_VERSION < 300
    if ({{left.getIntCheckExpression("*operand1")}} && {{right.getIntCheckExpression("operand2")}}) {
        {% import 'HelperSlotsInt.c.j2' as int_slots %}

        PyObject *result;
        PyObject *op1 = *operand1;

        {{ int_slots.int_slot(operator, nb_slot, object_desc, int_desc, int_desc, "result", "op1", "operand2", "exit_result", "exit_result_ok", "exit_result_exception") }}

        exit_result:

        if (unlikely(result == NULL)) {
            return false;
        }

        exit_result_ok:

        // We got an object handed, that we have to release.
        Py_DECREF(*operand1);

        // That's our return value then. As we use a dedicated variable, it's
        // OK that way.
        *operand1 = result;

        return true;

{# Not all operations have defined a definite exception exit, so this can be unused #}
        exit_result_exception:
        return false;
    }
#endif
{% endif %}

    if (Py_REFCNT(*operand1) == 1) {
        // We more or less own the operand, so we might re-use its storage and
        // execute stuff in-place.
{% if operator == "+" and left.mayBothHaveType(right, "str") %}
#if PYTHON_VERSION < 300
        if ({{left.getStringCheckExpression("*operand1")}} && !PyString_CHECK_INTERNED(*operand1) && {{right.getStringCheckExpression("operand2")}}) {
            return STRING_ADD_INPLACE(operand1, operand2);
        }
#endif
{% endif %}

{# Floats are very good for in-place operations. TODO: Probably a lot more could be added. #}
{% if operator in ("+", "-", "*") and left.hasOneOrBothType(right, "float") %}
        if ({{left.getFloatCheckExpression("*operand1")}} && {{right.getFloatCheckExpression("operand2")}}) {
            PyFloat_AS_DOUBLE(*operand1) {{operator}}= PyFloat_AS_DOUBLE(operand2);
            return true;
        }
{% endif %}

{% if operator == "+" and left.mayBothHaveType(right, "unicode") %}
#if PYTHON_VERSION >= 300
        if ({{left.getUnicodeCheckExpression("*operand1")}} && !PyUnicode_CHECK_INTERNED(*operand1) && {{right.getUnicodeCheckExpression("operand2")}}) {
            return UNICODE_ADD_INCREMENTAL(operand1, operand2);
        }
#endif
{% endif %}

{% if operator == "+" and left.hasOneOrBothType(right, "bytes") %}
#if PYTHON_VERSION >= 300
        if ({{left.getBytesCheckExpression("*operand1")}} && {{right.getBytesCheckExpression("operand2")}}) {
            return BYTES_ADD_INCREMENTAL(operand1, operand2);
        }
#endif
{% endif %}
    }

{% if operator == "+" and left.mayBothHaveType(right, "str") %}
#if PYTHON_VERSION < 300
    // Python2 strings are to be treated differently, fall back to Python API here.
    if ({{left.getStringCheckExpression("*operand1")}} && {{right.getStringCheckExpression("operand2")}}) {
        {# This could be inlined too, but it has some detail behind it. #}
        PyString_Concat(operand1, operand2);
        return !ERROR_OCCURRED();
    }
#endif
{% endif %}

{# Fast path for unicode values, very important for Python3, rare for Python2. #}
{% if operator == "+" and left.mayBothHaveType(right, "unicode") %}
    // Python3 Strings are to be treated differently.
    if ({{left.getUnicodeCheckExpression("*operand1")}} && {{right.getUnicodeCheckExpression("operand2")}}) {
        PyObject *result = UNICODE_CONCAT(*operand1, operand2);

        if (unlikely(result == NULL)) {
            return false;
        }

        Py_DECREF(*operand1);
        *operand1 = result;

        return true;
    }
{% endif %}

{# Fast path for list in-place add with at least one known list. #}
{% if operator == "+" and left.hasOneOrBothType(right, "list") %}
    if ({{left.getListCheckExpression("*operand1")}} && {{right.getListCheckExpression("operand2")}}) {
        return LIST_EXTEND_FROM_LIST(*operand1, operand2);
    }
{% endif %}

{# TODO: No special function for tuple += tuple yet. #}
{# Fast path for sequences in-place add with at least one known sequence type. #}
{% if operator == "+" and left.type_name in ("list", "tuple", "object") and right.type_name in ("list", "tuple", "object") and not (left.type_name == "object" == right.type_name) %}
    if ({{left.getSequenceCheckExpression("*operand1", right)}} && {{right.getSequenceCheckExpression("operand2", left)}}) {
        PyObject *result = PySequence_InPlaceConcat(*operand1, operand2);

        if (unlikely(result == NULL)) {
            return false;
        }

        Py_DECREF(*operand1);
        *operand1 = result;

        return true;
    }
{% endif %}

    {# TODO: There are major improvements possible by inlining this as well. #}
    PyObject *result = PyNumber_InPlace{{op_code.title()}}(*operand1, operand2);

    if (unlikely(result == NULL)) {
        return false;
    }

    // We got an object handed, that we have to release.
    Py_DECREF(*operand1);

    // That's our return value then. As we use a dedicated variable, it's
    // OK that way.
    *operand1 = result;

    return true;
}

bool BINARY_OPERATION_{{op_code}}_{{left.getHelperCodeName()}}_{{right.getHelperCodeName()}}_INPLACE({{left.getVariableDecl("*operand1")}}, {{right.getVariableDecl("operand2")}}) {
    return _BINARY_OPERATION_{{op_code}}_{{left.getHelperCodeName()}}_{{right.getHelperCodeName()}}_INPLACE(operand1, operand2);
}
